﻿using FingerTip.Framework.DependencyResolver;
using FingerTip.Framework.Domain;
using FingerTip.Framework.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace FingerTip.Framework.Commands
{
    public class CommandSender:ICommandSender
    {
        private readonly IResolver resolver;
        private readonly IEventPublisher eventPublisher;
        private readonly IEventStore eventStore;

        public CommandSender(IResolver resolver,
            IEventPublisher eventPublisher,
            IEventStore eventStore)
        {
            this.resolver = resolver;
            this.eventPublisher = eventPublisher;
            this.eventStore = eventStore;
        }

        public void Send<TCommand>(TCommand command,bool publishEvents=true) 
            where TCommand:ICommand
        {
            var commandHandler = GetHandler<ICommandHandler<TCommand>, TCommand>(command);
            var events = commandHandler.Handle(command);
            if (!publishEvents)
                return;
            foreach(var @event in events)
            {
                var concreteEvent = EventFactory.CreateConcreteEvent(@event);
                eventPublisher.Publish(concreteEvent);
            }
        }

        public void Send<TCommand,TAggregate>(TCommand command, bool publishEvents = true) 
            where TCommand : ICommand
            where TAggregate:IAggregateRoot
        {
            var commandHandler = GetHandler<ICommandHandler<TCommand>, TCommand>(command);
            var events = commandHandler.Handle(command);

            
            foreach (var @event in events)
            {
                var concreteEvent = EventFactory.CreateConcreteEvent(@event);
                eventStore.SaveEvent<TAggregate>((IDomainEvent)concreteEvent);

                if (!publishEvents)
                    continue;

                eventPublisher.Publish(concreteEvent);
            }
        }

        public async Task SendAsync<TCommand>(TCommand command,bool publishEvents=true) 
            where TCommand:ICommand
        {
            var commandHandler = GetHandler<ICommandHandlerAsync<TCommand>, TCommand>(command);
            var events = await commandHandler.HandleAsync(command);
            if (!publishEvents)
                return;
            foreach(var @event in events)
            {
                var concreteEvent = EventFactory.CreateConcreteEvent(@event);
                await eventPublisher.Publish(concreteEvent);
            }
        }

        public async Task SendAsync<TCommand,TAggregate>(TCommand command, bool publishEvents = true) 
            where TCommand : ICommand
            where TAggregate:IAggregateRoot
        {
            var commandHandler = GetHandler<ICommandHandlerAsync<TCommand>, TCommand>(command);
            var events = await commandHandler.HandleAsync(command);

            
            foreach (var @event in events)
            {
                var concreteEvent = EventFactory.CreateConcreteEvent(@event);
                await  eventStore.SaveEventAsync<TAggregate>((IDomainEvent)concreteEvent);

                if (!publishEvents)
                    continue;

                await eventPublisher.Publish(concreteEvent);
            }
        }

        private THandler GetHandler<THandler, TCommand>(TCommand command) where TCommand : ICommand
        {
            if (command == null)
            {
                throw new ArgumentNullException(nameof(command));
            }
            var commandHandler = resolver.Resolve<THandler>();
            if (commandHandler == null)
            {
                throw new Exception($"No Handler fount for command '{command.GetType().FullName}'");
            }
            return commandHandler;

        }
    }
}
